﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace AntMovement.Comm
{
    public class AntClient
    {
        private Socket _socket;
        private CancellationTokenSource _socketToken;
        private string _ip;
        private int _port;
        private AntProtocol _protocol;
        public AntClient(string ip, int port, CancellationTokenSource socketToken)
        {
            _ip = ip;
            _port = port;
            _socketToken = socketToken;
            _protocol = new AntProtocol();
        }

        public void Close()
        {
            if (_socket != null)
            {
                try
                {
                    byte[] result = SendToRead(new byte[] { 0x61 }, 3);
                    _socket.Close();
                    _socket.Dispose();
                }
                catch (Exception)
                {
                }
                _socket = null;
            }
        }

        public bool Open()
        {
            try
            {
                _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                _socket.ReceiveTimeout = 600;
                _socket.SendTimeout = 600;
                _socket.Connect(new IPEndPoint(IPAddress.Parse(_ip), _port));
                byte[] result = SendToRead(new byte[] { 0x60 }, 3);
                _protocol.SetHandle(result.Skip(2).Take(4).ToArray());
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return false;
            }
            return true;
        }

        #region function
        public object ReadSingle(string label, int count = 3)
        {
            List<byte> bytes = new List<byte>()
            {
                0x4C, 0x01
            };
            byte[] bytes_label = Encoding.ASCII.GetBytes(label);
            bytes.Add((byte)bytes_label.Length);
            bytes.AddRange(bytes_label);
            var list = SendToRead(bytes.ToArray(), count);
            if (list == null) { return default; };
            return ToValue(list[0], list.Skip(1).ToArray(), out _);
        }

        private object ToValue(byte datatype, byte[] bytes, out int count)
        {
            count = 0;
            switch (datatype)
            {
                case 0xC4: //"Int32"
                    count = 4;
                    return StructToBytes.GetValue(typeof(Int32), bytes, 0);
                case 0xC8: //"UInt32"
                    count = 4;
                    return StructToBytes.GetValue(typeof(UInt32), bytes, 0);
                case 0xCA://"Single"
                    count = 4;
                    return StructToBytes.GetValue(typeof(float), bytes, 0);
                case 0xC5://"Int64" 
                    count = 8;
                    return StructToBytes.GetValue(typeof(Int64), bytes, 0);
                case 0xC9: //"UInt64"
                    count = 8;
                    return StructToBytes.GetValue(typeof(UInt64), bytes, 0);
                case 0xCB://"Double"
                    count = 8;
                    return StructToBytes.GetValue(typeof(Double), bytes, 0);
                case 0xC3: //"Int16"
                    count = 2;
                    return StructToBytes.GetValue(typeof(Int16), bytes, 0);
                case 0xC7: //"UInt16"
                    count = 2;
                    return StructToBytes.GetValue(typeof(UInt16), bytes, 0);
                case 0xD1://"Byte"
                    count = 1;
                    return StructToBytes.GetValue(typeof(Byte), bytes, 0);
                case 0xC1://"Boolean"
                    count = 1;
                    return StructToBytes.GetValue(typeof(Boolean), bytes, 0);
                case 0xD0: //"String"
                    int length = bytes[0] + (bytes[1] << 8);
                    count = length + 2;
                    return Encoding.ASCII.GetString(bytes.Skip(2).Take(length).ToArray());
            }
            throw new ArgumentException("This type is not implemented...");
        }

        public bool WriteSingle(object value, string label, int count = 3)
        {
            List<byte> bytes = new List<byte>
            {
                0x4D, 0x01
            };
            byte[] bytes_label = Encoding.ASCII.GetBytes(label);
            bytes.Add((byte)bytes_label.Length);
            bytes.AddRange(bytes_label);
            byte type_cipdata = 0;
            int bytes_length = 0;
            switch (value.GetType().Name)
            {
                case "Int32": type_cipdata = 0xC4; bytes_length = 4; break;
                case "UInt32": type_cipdata = 0xC8; bytes_length = 4; break;
                case "Single": type_cipdata = 0xCA; bytes_length = 4; break;
                case "Int64": type_cipdata = 0xC5; bytes_length = 8; break;
                case "UInt64": type_cipdata = 0xC9; bytes_length = 8; break;
                case "Double": type_cipdata = 0xCB; bytes_length = 8; break;
                case "Int16": type_cipdata = 0xC3; bytes_length = 2; break;
                case "UInt16": type_cipdata = 0xC4; bytes_length = 2; break;
                case "Byte": throw new NotImplementedException("未实现");
                case "Boolean": throw new NotImplementedException("未实现");
                case "String":
                    type_cipdata = 0xD0;
                    bytes_length = value.ToString().Length;
                    break;
            }
            bytes.Add(type_cipdata);
            byte[] value_bytes = default;
            if (value.GetType().Name == "String")
            {
                value_bytes = new byte[bytes_length + 2];
                value_bytes[0] = (byte)(bytes_length);
                value_bytes[1] = (byte)(bytes_length >> 8);
                var ascii = Encoding.ASCII.GetBytes(value.ToString());
                Array.Copy(ascii, 0, value_bytes, 2, ascii.Length);
            }
            else
            {
                value_bytes = new byte[bytes_length];
                StructToBytes.GetBytes(value, value_bytes, 0);
            }
            bytes.AddRange(value_bytes);
            var list = SendToRead(bytes.ToArray(), count);
            return !(list == null);
        }

        public TClass ReadClass<TClass>(string label, TClass obj = null, int count = 3) where TClass : class
        {
            if (obj == null) { obj = Activator.CreateInstance<TClass>(); }
            List<byte> list = new List<byte>()
            {
                0x4C, 0x01
            };
            byte[] bytes_label = Encoding.ASCII.GetBytes(label);
            list.Add((byte)bytes_label.Length);
            list.AddRange(bytes_label);
            var bytes = SendToRead(list.ToArray(), count);
            if (bytes == null) { return default; }
            int boolSize = 0;
            StructToBytes.FromBytes(obj, bytes.Skip(1).ToArray(), ref boolSize, 0);
            return obj;
        }
        #endregion

        #region Synchronize
        private volatile int _lock = 0;
        protected void Locked()
        {
            int lockNumber = 0;
            while (Interlocked.Exchange(ref _lock, 1) != 0)
            {
                _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0; _ = 0;
                if (++lockNumber > 50)
                {
                    Thread.Sleep(1);
                    lockNumber = 0;
                }
            }
        }

        protected void UnLock() => Interlocked.Exchange(ref _lock, 0);
        #endregion

        private byte[] SendToRead(byte[] bytes, int count)
        {
            for (int i = 0; i < count; i++)
            {
                try
                {
                    Locked();
                    _socket.Send(_protocol.Serialize(bytes));
                    List<byte> list = new List<byte>();
                    byte[] buffer = new byte[1024];
                    int length = _socket.Receive(buffer);
                    list.AddRange(buffer.Take(length));
                    while (!_protocol.Verify(list))
                    {
                        length = _socket.Receive(buffer);
                        list.AddRange(buffer.Take(length));
                    }
                    UnLock();
                    return _protocol.Deserialize(list);
                }
                catch (Exception ex)
                {
                    UnLock();
                    if (count == 1
                        || (_socketToken != null && _socketToken.Token.IsCancellationRequested)
                        || ex.ToString().Contains("远程主机强迫关闭"))
                    {
                        throw ex;
                    }
                }
            }
            return default;
        }
    }
}
